병원 검진 예약 사이트 만들기
✒️ 2025-07-06 21:34 내용 수정
실습 목표
- 병원 예약 관리 시스템을 만들어 예약 목록을 조회하고 보안 기능을 추가한다.
- Reservation Entity 정보
| 이름 | 타입 | 설명 |
|---|---|---|
| reservationId | int | 예약 ID |
| patientName | String | 환자명 |
| patientPhone | String | 환자 연락처 |
| doctorName | String | 의사명 |
| department | String | 진료과 |
| reservationDate | Date | 예약 날짜 |
| reservationTime | Time | 예약 시간 |
| symptoms | String | 증상 |
| status | String | 예약 상태 |
| createdAt | Timestamp | 등록일시 |
- 보안 요구 사항
- XSS 방지를 위한 출력값 이스케이프 처리
- 전화번호 마스킹
- SQL Injection 방지를 위한 PreparedStatement 사용
프로젝트 설정
의존성
- JSP, JSTL 의존성을 추가하여 JSP 설정을 적용한다.
- PostgreSQL 의존성을 추가하여 데이터 베이스 연결을 설정했다.
<!-- JSP API -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- PostgreSQL JDBC Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
디렉터리 구조
reservation/
├── src/
│ ├── main/
│ ├── java/
│ │ ├── dao/
│ │ │ └── ReservationDAO.java
│ │ ├── dto/
│ │ │ └── Reservation.java
│ │ ├── servlet/
│ │ │ └── ReservationListServlet.java
│ │ ├── util/
│ │ ├── DBConnection.java
│ │ └── SecurityUtil.java
│ ├── resources/
│ ├── webapp/
│ ├── WEB-INF/
│ │ ├── views/
│ │ │ ├── error.jsp
│ │ │ └── reservationList.jsp
│ │ └── web.xml
│ └── index.jsp
└── pom.xml
database 설정
- SQL을 사용하여 환자 예약 정보 Database 스키마와 데이터베이스를 추가하고, 테스트 데이터를 추가한다.
-- 데이터베이스 생성
CREATE DATABASE hospital_reservation_system;
-- reservations 테이블 생성
CREATE TABLE reservations (
reservation_id SERIAL PRIMARY KEY,
patient_name VARCHAR(100) NOT NULL,
patient_phone VARCHAR(20) NOT NULL,
doctor_name VARCHAR(100) NOT NULL,
department VARCHAR(50) NOT NULL,
reservation_date DATE NOT NULL,
reservation_time TIME NOT NULL,
symptoms TEXT,
status VARCHAR(20) DEFAULT '예약완료' CHECK (status IN ('예약완료', '진료완료', '취소')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 초기 예약 데이터 삽입
INSERT INTO reservations (patient_name, patient_phone, doctor_name, department, reservation_date, reservation_time, symptoms, status) VALUES
('김환자', '010-1234-5678', '김내과', '내과', '2025-06-20', '09:00', '복통 및 소화불량 증상', '예약완료'),
('이환자', '010-2345-6789', '이외과', '외과', '2025-06-20', '10:30', '복부 종양 검사', '예약완료'),
('박환자', '010-3456-7890', '박정형', '정형외과', '2025-06-21', '14:00', '무릎 통증', '진료완료'),
('최환자', '010-4567-8901', '최피부', '피부과', '2025-06-22', '16:30', '피부 트러블', '예약완료'),
('정환자', '010-5678-9012', '정안과', '안과', '2025-06-23', '11:00', '시력 저하', '취소'),
('한환자', '010-6789-0123', '김내과', '내과', '2025-06-24', '15:00', '정기 검진', '예약완료');
-- 데이터 확인
SELECT * FROM reservations ORDER BY reservation_date, reservation_time;
MVC 패턴
Util
- DB 설정, 보안 설정 파일을 추가했다.
DB Connection
- PostgreSQL과 연결하는 클래스로,
Connection객체를 반환한다. - URL에는 JDBC 드라이버, 호스트, 포트 번호, 그리고 연결할 데이터 베이스 이름을 추가한다.
- 사용자 정보에는 PostgreSQL 사용자 계정과 비밀번호를 추가하고 이 정보를 사용해서 데이터 베이스에 연결한다.
package util;
import java.sql.Connection;
import java.sql.DriverManager;
public class DBConnection {
private static final String url = "jdbc:postgresql://localhost:5432/hospital_reservation_system";
private static final String user = "postgres";
private static final String password = "password";
static {
try {
// 드라이버 클래스 탐색
Class.forName("org.postgresql.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
return DriverManager.getConnection(url, user, password);
}
}
SecurityUtil
- 보안 관련 설정은 수업때 받은 코드로 사용했다.
- HTML 이스케이프
- sanitize-html 라이브러리처럼
escapeHtml에는 input을 받아서 HTML 태그 요소에 해당하는<>등의 기호를 HTML 엔티티 코드로 처리한다. removeScripts함수는SCRIPT_PATTERN으로 지정한 스크립트 태그 정규 표현식과 일치하는 String 내용물을 지운다.sanitizeString함수에선 위 두 함수를 사용해서 스크립트 태그를 제거하고, HTML 태그가 될 수 있는 기호들을 모두 엔티티 코드로 처리해서 스크립트가 들어오지 않도록 만든다.
- sanitize-html 라이브러리처럼
- 전화번호 마스킹
- 전화번호 자릿수에 따라 중간 번호 부분을
*표시로 처리한다.
- 전화번호 자릿수에 따라 중간 번호 부분을
package util;
import java.util.regex.Pattern;
public class SecurityUtil {
private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<[^>]*>");
private static final Pattern SCRIPT_PATTERN = Pattern.compile("(?i)<script[^>]*>.*?</script>");
public static String escapeHtml(String input) {
if (input == null) return null;
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
.replace("/", "/");
}
public static String removeScripts(String input) {
if (input == null) return null;
return SCRIPT_PATTERN.matcher(input).replaceAll("");
}
public static String sanitizeString(String input) {
if (input == null) return null;
String scriptRemovedInput = removeScripts(input);
return escapeHtml(scriptRemovedInput);
}
public static String maskPhoneNumber(String phone) {
if (phone == null || phone.length() < 8) return phone;
String cleaned = phone.replaceAll("[^0-9]", "");
if (cleaned.length() == 11) {
return cleaned.substring(0, 3) + "-****-" + cleaned.substring(7);
} else if (cleaned.length() == 10) {
return cleaned.substring(0, 3) + "-***-" + cleaned.substring(6);
}
return phone;
}
}
Model
DTO
- DTO에는 private으로 설정된 필드, 생성자, getter, setter를 작성한다.
package dto;
import java.sql.Time;
import java.sql.Date;
import java.sql.Timestamp;
public class Reservation {
private int reservationId;
private String patientName;
private String patientPhone;
private String doctorName;
private String department;
private Date reservationDate;
private Time reservationTime;
private String symptoms;
private String status;
private Timestamp createdAt;
public Reservation() {}
public Reservation(
int reservationId, String patientName, String patientPhone,
String doctorName, String department, Date reservationDate,
Time reservationTime, String symptoms,
String status, Timestamp createdAt
) {
this.reservationId = reservationId;
this.patientName = patientName;
this.patientPhone = patientPhone;
this.doctorName = doctorName;
this.department = department;
this.reservationDate = reservationDate;
this.reservationTime = reservationTime;
this.symptoms = symptoms;
this.status = status;
this.createdAt = createdAt;
}
// setter와 getter
}
DAO
- DAO는 데이터 베이스에 연결한 후 DB와의 상호 작용을 관리한다.
- 조회할 데이터를 가져오는 SQL문을 사용하여
Connection객체로 데이터 베이스에 데이터를 요청한다.
- 조회할 데이터를 가져오는 SQL문을 사용하여
package dao;
import dto.Reservation;
import util.DBConnection;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class ReservationDAO {
private static ReservationDAO instance = new ReservationDAO();
private ReservationDAO() {}
public static ReservationDAO getInstance() {
return instance;
}
public List<Reservation> selectList() {
List<Reservation> list = new ArrayList<>();
String sql = "SELECT * FROM reservations";
try {
Connection conn = DBConnection.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
Reservation rev = new Reservation();
rev.setReservationId(rs.getInt("reservation_id"));
rev.setPatientName(rs.getString("patient_name"));
rev.setPatientPhone(rs.getString("patient_phone"));
rev.setDoctorName(rs.getString("doctor_name"));
rev.setDepartment(rs.getString("department"));
rev.setReservationDate(rs.getDate("reservation_date"));
rev.setReservationTime(rs.getTime("reservation_time"));
rev.setSymptoms(rs.getString("symptoms"));
rev.setStatus(rs.getString("status"));
rev.setCreatedAt(rs.getTimestamp("created_at"));
list.add(rev);
}
rs.close();
pstmt.close();
conn.close();
} catch (Exception e){
e.printStackTrace();
}
return list;
}
}
Controller
Servlet
- Mapping은
/reservations으로 설정해서 해당 요청을ReservationListServlet에서 처리한다. - DAO 객체의 인스턴스(Singleton으로 되어 있음)를 가져오고, DAO를 통해 데이터 베이스에 정보를 요청한 뒤
List에 담아 Dispatcher로 JSP에 데이터를 담아 반환한다. - 혼자 코드를 작성할 때는 원래 출력 시 이스케이프 처리가 요구사항에 있어 출력 내용을 이스케이프 처리했다.
package servlet;
import dao.ReservationDAO;
import dto.Reservation;
import util.SecurityUtil;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/reservations")
public class ReservationListServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
ReservationDAO dao = ReservationDAO.getInstance();
List<Reservation> list = dao.selectList();
List<Reservation> securedList = new ArrayList<>();
// 데이터 가져오기
for(Reservation res : list) {
Reservation secRes = new Reservation(
res.getReservationId(),
SecurityUtil.sanitizeString(res.getPatientName()),
SecurityUtil.maskPhoneNumber(res.getPatientPhone()),
SecurityUtil.sanitizeString(res.getDoctorName()),
SecurityUtil.sanitizeString(res.getDepartment()),
res.getReservationDate(),
res.getReservationTime(),
SecurityUtil.sanitizeString(res.getSymptoms()),
SecurityUtil.sanitizeString(res.getStatus()),
res.getCreatedAt()
);
securedList.add(secRes);
}
request.setAttribute("list", securedList);
RequestDispatcher disp = request.getRequestDispatcher("/WEB-INF/views/reservationList.jsp");
disp.forward(request, response);
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("errorMessage", "데이터를 가져오는 데 실패했습니다.");
RequestDispatcher disp = request.getRequestDispatcher("/WEB-INF/views/error.jsp");
disp.forward(request, response);
}
}
}
View
main
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"
errorPage="error.jsp"
%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>예약 목록</title>
<style>
table, tr, th, td{
border-collapse: collapse;
border : 1px solid black;
text-align: center;
}
th {
background-color: #f1f1f1;
}
tr, th, td {
padding: 5px;
}
</style>
</head>
<body>
<table>
<tr>
<th>예약ID</th>
<th>환자명</th>
<th>환자 연락처</th>
<th>의사명</th>
<th>진료과</th>
<th>예약날짜</th>
<th>예약시간</th>
<th>증상</th>
<th>예약상태</th>
<th>등록일시</th>
</tr>
<c:forEach var="rev" items="${list}">
<tr>
<td>${rev.reservationId}</td>
<td>${rev.patientName}</td>
<td>${rev.patientPhone}</td>
<td>${rev.doctorName}</td>
<td>${rev.department}</td>
<td>${rev.reservationDate}</td>
<td>${rev.reservationTime}</td>
<td>${rev.symptoms}</td>
<td>${rev.status}</td>
<td><fmt:formatDate value="${rev.createdAt}"
pattern="yyyy-MM-dd HH:mm"/></td>
</tr>
</c:forEach>
</table>
</body>
</html>
error page
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
isErrorPage="true" isELIgnored="false"
%>
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>에러 페이지</title>
</head>
<body>
<h2>에러가 발생했습니다</h2>
<p>${errorMessage}</p>
</body>
</html>
테스트
- 웹 브라우저에서
http:/localhost:port/project-name/reservations으로 접속해서 데이터를 확인한다.